package map;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.util.ArrayList;
import java.util.List;
import objects.GameObject;

/**
 * The tile class represents a single space on the map
 * 
 * @author Kyle_Solo
 */
public class Tile {

	/**
	 * Temporary
	 */
	private static Polygon hex;
	/**
	 * Temporary: tiles don't care how large they are
	 */
	private static int tileHeight = 48;
	/**
	 * Temporary: tiles don't care how large they are
	 */
	private static int tileWidth = 48;
	static {
		// temporary code that renders a hexagon outline instead of an image
		int qx = tileWidth / 4;
		int qy = tileHeight / 4;
		hex = new Polygon();
		hex.addPoint(-2 * qx, 0);
		hex.addPoint(-qx, -2 * qy);
		hex.addPoint(qx, -2 * qy);
		hex.addPoint(2 * qx, 0);
		hex.addPoint(qx, 2 * qy);
		hex.addPoint(-qx, 2 * qy);
	}
	/**
	 * This tile's hexagonal coordinate, a
	 */
	public final int a;
	/**
	 * This tile's hexagonal coordinate, b
	 */
	public final int b;
	/**
	 * This tile's hexagonal coordinate, c
	 */
	public final int c;
	/**
	 * Temporary color variable to distinguish between tiles
	 */
	private final Color color = Color.getHSBColor((float) Math.random(), 0.1f, 0.5f);
	private final Map map;
	private ArrayList<GameObject> objects;

	/**
	 * This tile's rectangular coordinate, x
	 */
	public final int x;
	/**
	 * This tile's rectangular coordinate, y
	 */
	public final int y;

	/**
	 * Constructs a new tile. For ease of use, the tile is passed its location in the map.
	 * 
	 * @param m
	 * @param x
	 * @param y
	 */
	public Tile(Map m, int x, int y) {
		// convert x,y to hex coordinates
		a = Map.getA(x, y);
		b = Map.getB(x, y);
		c = Map.getC(x, y);

		this.x = x;
		this.y = y;
		map = m;

		assert a + b + c == 0 : "Coordinate transformation failed!";
		assert Map.getX(a, b, c) == x : "Coordinate transformation failed!";
		assert Map.getY(a, b, c) == y : "Coordinate transformation failed!";
	}

	/**
	 * Adds an object to a tile from empty space. Triggers the onOtherEnter event and the object's
	 * onEnter event.
	 * 
	 * @param gameObject
	 */
	public void addObject(GameObject gameObject) {
		if (objects == null) {
			objects = new ArrayList<GameObject>();
		}
		gameObject.currentTile = this;
		objects.add(gameObject);
		triggerEnterEvents(gameObject);
	}

	/**
	 * This method returns a list of all the GameObjects on this tile, or null if nothing is on the
	 * tile. It is inadvisable to alter this list, as that would have unpredictable results.
	 * 
	 * @return the list
	 */
	public List<GameObject> getObjects() {
		if (objects != null) {
			return objects;
		} else {
			return null;
		}
	}

	/**
	 * @param da
	 *            delta a
	 * @param db
	 *            delta b
	 * @param dc
	 *            delta c
	 * @return a tile offset from this one by the specified amount
	 */
	public Tile getTile(int da, int db, int dc) {
		System.out.println("moving creature from " + a + "," + b + "," + c);
		Tile t = map.getTile(a + da, b + db, c + dc);
		if (t != null) {
			System.out.println("to " + t.a + "," + t.b + "," + t.c);
		}
		return t;
	}

	/**
	 * Moves a GameObject from this tile to the target, triggering enter and exit events.
	 * 
	 * @param target
	 *            the tile to move the GameObject to
	 * @param gameObject
	 *            the GameObject to move
	 */
	public void moveObject(Tile target, GameObject gameObject) {
		assert objects != null : "Tile tried to move a GameObject, but it was empty!";
		assert objects.contains(gameObject) : "Tile tried to move a GameObject it did not own!";
		assert target != null : "Tried to move to a null Tile!";
		assert gameObject != null : "Tried to move a null GameObject!";
		triggerExitEvents(gameObject);
		if ((target.objects == null || target.objects.size() == 0) && objects.size() == 1) {
			/*
			 * If this tile only contains one GameObject, and the other tile is empty, then move the
			 * list to save time making a new one
			 */
			target.objects = objects;
			objects = null;
		} else {

			if (target.objects == null) {
				target.objects = new ArrayList<GameObject>();
			}
			objects.remove(gameObject);
			target.objects.add(gameObject);
		}
		gameObject.currentTile = target;
		target.triggerEnterEvents(gameObject);
		assert objects == null || !objects.contains(gameObject) : "Moving a GameObject failed!  GameObject still on Tile.";
		assert target.objects.contains(gameObject) : "Moving a GameObject failed!  GameObject did not reach destination Tile.";
	}

	/**
	 * Draws the tile to the view.
	 * 
	 * @param g2d
	 * @param x2
	 * @param y2
	 */
	public void render(Graphics2D g2d, int x2, int y2) {
		g2d.translate(x2, y2);
		// this is where the tile will be drawn someday
		g2d.setColor(color);
		g2d.fillPolygon(hex);
		g2d.setColor(Color.black);
		g2d.drawPolygon(hex);
		g2d.drawString(a + "," + b + "," + c, 0, 0);
		if (objects != null) {
			for (GameObject g : objects) {
				g.render(g2d);
			}
		}
		g2d.translate(-x2, -y2);
	}

	private void triggerEnterEvents(GameObject gameObject) {
		assert objects != null : "Tile tried to call enter events on a GameObject it did not own!";
		assert objects.contains(gameObject) : "Tile tried to call enter events on a GameObject it did not own!";
		// call all the onOtherEnter events
		for (GameObject t : objects) {
			if (t != gameObject) {
				t.onOtherEnter(gameObject);
			}
		}
		// call the GameObject's onEnter event
		gameObject.onEnter(this);
	}

	private void triggerExitEvents(GameObject gameObject) {
		assert objects != null : "Tile tried to call exit events on a GameObject it did not own!";
		assert objects.contains(gameObject) : "Tile tried to call exit events on a GameObject it did not own!";
		// call all the onOtherExit events
		for (GameObject t : objects) {
			if (t != gameObject) {
				t.onOtherExit(gameObject);
			}
		}
		// call the GameObject's onExit event
		gameObject.onExit(this);
	}
}
